from __future__ import annotations
import json
import base64
import requests
import frappe
from ..utils.YT import YunExpressClient as YT


@frappe.whitelist(allow_guest=True)
def get_issues_by_orders(order_names):
	"""
	根据销售订单列表，查询关联的 Issue
	依赖自定义字段：Issue.custom_orders (Link -> Sales Order)
	Args:
	    order_names (list or str): 销售订单名数组，或其 JSON 字符串
	Returns:
	    dict: { "SO-0001": [ {name, status, creation}, ... ], ... }
	"""
	try:
		if isinstance(order_names, str):
			order_list = json.loads(order_names) if order_names.strip() else []
		else:
			order_list = order_names or []
		if not isinstance(order_list, list):
			order_list = []

		if not order_list:
			return {"success": True, "data": {}}

		issues = frappe.get_all(
			"Issue",
			filters={"custom_orders": ["in", order_list]},
			fields=["name", "status", "creation", "custom_orders"],
			order_by="creation desc",
			limit_page_length=0,
		)
		mapping = {}
		for it in issues:
			so = it.get("custom_orders")
			if not so:
				continue
			mapping.setdefault(so, []).append(
				{"name": it.get("name"), "status": it.get("status"), "creation": it.get("creation")}
			)
		return {"success": True, "data": mapping}
	except Exception as e:
		frappe.logger().error(f"[Issue] get_issues_by_orders 失败: {str(e)}")
		return {"success": False, "message": str(e), "data": {}}


@frappe.whitelist(allow_guest=True)
def get_logistics_orders_by_status(order_names, mode: str = "exceptions"):
	"""
	根据销售订单列表，按物流状态分类返回订单名列表。

	mode:
	- exceptions: 异常（logistics_status_code in ('E','N') 或 issue_status='Processing'）
	- in_transit: 运输中（除 exceptions / delivered 之外的记录）
	- delivered: 已送达（logistics_status_code = 'D'）
	"""
	try:
		if isinstance(order_names, str):
			order_list = json.loads(order_names) if order_names.strip() else []
		else:
			order_list = order_names or []
		if not isinstance(order_list, list):
			order_list = []

		if not order_list:
			return {"success": True, "orders": []}

		mode = (mode or "exceptions").lower()

		# 先拉取相关 Logistics Order Inst 记录
		rows = frappe.get_all(
			"Logistics Order Inst",
			filters={"sales_order": ["in", order_list]},
			fields=["sales_order", "logistics_status_code", "issue_status"],
			limit_page_length=0,
		)

		exception_set = set()
		delivered_set = set()
		in_transit_set = set()

		for r in rows:
			so = r.get("sales_order")
			if not so:
				continue
			raw_code = (r.get("logistics_status_code") or "").strip()
			# 兼容 'D:Delivered' 这一类完整字符串，只取冒号前首段作为状态码
			code = (raw_code.split(":", 1)[0] or "").strip().upper()
			issue_status = (r.get("issue_status") or "").strip()

			is_exception = code in {"E", "N"} or issue_status == "Processing"
			is_delivered = code == "D"

			if is_exception:
				exception_set.add(so)
			elif is_delivered:
				delivered_set.add(so)
			else:
				in_transit_set.add(so)

		if mode == "delivered":
			target = delivered_set
		elif mode == "in_transit":
			target = in_transit_set
		else:
			target = exception_set

		return {"success": True, "orders": list(target)}
	except Exception as e:
		frappe.logger().error(f"[LOS Tracking] get_logistics_orders_by_status 失败: {str(e)}")
		return {"success": False, "message": str(e), "orders": []}


@frappe.whitelist()
def set_logistics_issue_for_order(sales_order: str, issue_name: str):
	"""
	为指定销售订单关联 Issue，并回写到最新的 Logistics Order Inst：
	- issue: Issue 名称
	- issue_raised_at: 当前时间
	- issue_status: Processing
	"""
	try:
		if not sales_order or not issue_name:
			return {"success": False, "message": "sales_order 和 issue_name 均不能为空"}

		los_rows = frappe.get_all(
			"Logistics Order Inst",
			filters={"sales_order": sales_order},
			fields=["name"],
			order_by="creation desc",
			limit=1,
		)
		if not los_rows:
			return {"success": False, "message": f"未找到销售订单 {sales_order} 对应的 Logistics Order Inst 记录"}

		los_name = los_rows[0].name
		doc = frappe.get_doc("Logistics Order Inst", los_name)
		now = frappe.utils.now()
		if doc.meta.has_field("issue"):
			doc.db_set("issue", issue_name, update_modified=False)
		if doc.meta.has_field("issue_raised_at"):
			doc.db_set("issue_raised_at", now, update_modified=False)
		if doc.meta.has_field("issue_status"):
			doc.db_set("issue_status", "Processing", update_modified=False)

		frappe.db.commit()
		return {"success": True, "los_name": los_name}
	except Exception as e:
		frappe.logger().error(f"[LOS Tracking] set_logistics_issue_for_order 失败: {str(e)}")
		return {"success": False, "message": str(e)}

@frappe.whitelist(allow_guest=True)
def update_issue_resolution(issue_name: str, resolution: str):
	"""
	更新 Issue 的问题解决/处理说明字段（兼容不同字段名）
	优先使用标准字段 resolution，不存在则尝试 resolution_details
	"""
	try:
		if not issue_name:
			return {"success": False, "message": "缺少 issue_name"}
		doc = frappe.get_doc("Issue", issue_name)
		data = doc.as_dict()
		if "resolution" in data:
			doc.set("resolution", resolution or "")
		elif "resolution_details" in data:
			doc.set("resolution_details", resolution or "")
		else:
			old_desc = doc.get("description") or ""
			doc.set("description", f"{old_desc}\n\n[Resolution]\n{resolution or ''}")
		doc.save(ignore_permissions=True)
		return {"success": True}
	except Exception as e:
		frappe.logger().error(f"[Issue] 更新 resolution 失败: {str(e)}")
		return {"success": False, "message": str(e)}


@frappe.whitelist(allow_guest=True)
def upload_issue_attachments(issue_name: str, files):
	"""
	批量上传附件到指定 Issue
	Args:
	    issue_name (str)
	    files (list or str): [{filename, data}] data 为 base64（不含 data: 前缀）
	"""
	try:
		if isinstance(files, str):
			file_list = json.loads(files) if files.strip() else []
		else:
			file_list = files or []
		from frappe.utils.file_manager import save_file

		saved = []
		for f in file_list:
			fname = (f or {}).get("filename")
			data_b64 = (f or {}).get("data")
			if not fname or not data_b64:
				continue
			filedoc = save_file(
				fname,
				data_b64,
				"Issue",
				issue_name,
				decode=True,
				is_private=1,
			)
			saved.append({"file_url": filedoc.file_url, "name": filedoc.name})
		return {"success": True, "data": saved}
	except Exception as e:
		frappe.logger().error(f"[Issue] 附件上传失败: {str(e)}")
		return {"success": False, "message": str(e), "data": []}



@frappe.whitelist(allow_guest=True)
def yt_test_query_work_orders():
	"""
	云途 WOS v2 工单接口权限连通性测试：/api/v2/workOrder/queryWorkOrders
	返回 HTTP 状态码与响应体，方便判断是否有权限（即便参数报错但能返回业务码，也说明有权限）。
	"""
	try:
		yt = YT()
		uri = "/api/v2/workOrder/queryWorkOrders"
		body = {"pageNumber": 1, "pageSize": 1}
		token = yt.get_access_token()
		headers = yt.get_headers(token, body, uri, "POST")
		canonical = yt._canonicalize_body(body)
		if isinstance(canonical, str):
			canonical = canonical.encode("utf-8")
		url = "https://wosv2api.yunexpress.cn" + uri
		resp = requests.post(url, headers=headers, data=canonical, timeout=20)
		status = resp.status_code
		data = None
		text = None
		try:
			data = resp.json()
		except Exception:
			text = resp.text
		return {"success": 200 <= status < 300, "http_status": status, "data": data, "text": text}
	except Exception as e:
		frappe.logger().error(f"[YT Test] queryWorkOrders failed: {str(e)}")
		return {"success": False, "error": str(e)}


@frappe.whitelist()
def yt_update_fee_for_los(los_name: str, order_number: str | None = None):
	"""
	根据 YunExpress 订单费用详情接口，回填到 Logistics Order Inst：
	- billing_currency
	- billed_amount
	- actual_weight
	- charge_weight
	- fee_detail_b64（承运商费用明细的原始 JSON，用于对账）
	优先使用入参 order_number；未传则从记录中读取 waybill_number / tracking_number 依次尝试。
	"""
	try:
		if not los_name:
			return {"success": False, "message": "缺少 los_name"}

		doc = frappe.get_doc("Logistics Order Inst", los_name)

		ord_no = (order_number or "").strip()
		if not ord_no:
			ord_no = (doc.get("waybill_number") or "").strip()
		if not ord_no:
			ord_no = (doc.get("tracking_number") or "").strip()

		if not ord_no:
			return {"success": False, "message": "未找到可用于查询的订单号码"}

		yt = YT()
		# 记录请求日志，便于排查问题
		# 云途文档：/v1/order/fee-details/get 使用参数 waybill_number（运单号）
		frappe.logger().error(f"[YT Fee] Request get_order_fee_detail: waybill_number={ord_no}")
		resp = yt.get_order_fee_detail({"waybill_number": ord_no})
		try:
			frappe.logger().error(f"[YT Fee] Response get_order_fee_detail: waybill_number={ord_no}, resp={json.dumps(resp, ensure_ascii=False)[:2000]}")
		except Exception:
			frappe.logger().error(f"[YT Fee] Response get_order_fee_detail (raw): order_number={ord_no}, resp={resp}")
		result = (resp or {}).get("result") or {}

		# 仅保存你要求展示的几个关键字段
		currency = result.get("currency")
		total_amount = result.get("total_amount")
		charge_weight = result.get("charge_weight")
		actual_weight = result.get("actual_weight")
		transaction_date = result.get("transaction_date") or result.get("transactionTime")

		# 费用明细，以原始 JSON 形式保存，方便对账 / 明细表落地
		fee_details = result.get("fee_details") or []
		try:
			fee_json = json.dumps(fee_details, ensure_ascii=False, separators=(",", ":"))
		except Exception:
			fee_json = ""

		# 回填（使用 db_set 以允许提交后更新）
		if currency:
			doc.db_set("billing_currency", currency, update_modified=False)
		if total_amount is not None:
			doc.db_set("billed_amount", total_amount, update_modified=False)
		if charge_weight is not None:
			doc.db_set("charge_weight", charge_weight, update_modified=False)
		if actual_weight is not None:
			doc.db_set("actual_weight", actual_weight, update_modified=False)
		if fee_json:
			doc.db_set("fee_detail_b64", fee_json, update_modified=False)

		return {
			"success": True,
			"data": {
				"currency": currency,
				"total_amount": total_amount,
				"actual_weight": actual_weight,
				"charge_weight": charge_weight,
				"transaction_date": transaction_date,
				"fee_details": fee_details,
			},
		}
	except Exception as e:
		frappe.logger().error(f"[YT Fee] 更新失败: {str(e)}")
		return {"success": False, "message": str(e)}


def create_or_update_logistics_order_inst(
	sales_order_name: str,
	waybill_number: str | None = None,
	tracking_number: str | None = None,
	product_code: str | None = None,
	warehouse_weight: float | None = None,
	receiver_info: dict | None = None,
	shopify_order_json: str | None = None,
	package_status: str | None = None,
):
	"""
	创建或更新 Logistics Order Inst 记录
	
	Args:
		sales_order_name: 销售订单名称（必需）
		waybill_number: 运单号
		tracking_number: 跟踪号
		product_code: 物流产品代码
		warehouse_weight: 仓库称重重量（kg）
		receiver_info: 收件人信息字典
		shopify_order_json: Shopify订单JSON字符串
		package_status: 云途当前包裹状态代码（F/T/P/E/R/C/D 等），用于初始化 logistics_status_code
	
	Returns:
		dict: {"success": bool, "los_name": str | None, "message": str}
	"""
	try:
		if not sales_order_name:
			return {"success": False, "los_name": None, "message": "缺少销售订单名称"}

		# 规范化云途状态码（单字母大写）
		status_code = None
		if package_status:
			code = str(package_status).strip().upper()
			if code:
				# 只取首字母，兼容意外传入 "D:Delivered" / "delivered" 之类
				status_code = code[0]

		# 查找是否已存在记录
		existing = frappe.get_all(
			"Logistics Order Inst",
			filters={"sales_order": sales_order_name},
			fields=["name"],
			limit=1,
		)

		if existing:
			# 更新现有记录
			doc = frappe.get_doc("Logistics Order Inst", existing[0].name)
			update_fields = {}
			
			if waybill_number:
				update_fields["waybill_number"] = waybill_number
			if tracking_number:
				update_fields["tracking_number"] = tracking_number
			if product_code:
				update_fields["yt_product_code"] = product_code
			if warehouse_weight is not None:
				update_fields["warehouse_weight"] = float(warehouse_weight)
			if receiver_info:
				if receiver_info.get("name") or receiver_info.get("first_name"):
					update_fields["recipient_name"] = (
						receiver_info.get("name") 
						or f"{receiver_info.get('first_name', '')} {receiver_info.get('last_name', '')}".strip()
					)
				if receiver_info.get("phone") or receiver_info.get("phone_number"):
					update_fields["recipient_phone"] = receiver_info.get("phone") or receiver_info.get("phone_number")
				if receiver_info.get("email") or receiver_info.get("email_id"):
					update_fields["recipient_email"] = receiver_info.get("email") or receiver_info.get("email_id")
				if receiver_info.get("country") or receiver_info.get("country_code"):
					update_fields["recipient_country"] = receiver_info.get("country") or receiver_info.get("country_code")
				if receiver_info.get("state") or receiver_info.get("province"):
					update_fields["recipient_state"] = receiver_info.get("state") or receiver_info.get("province")
				if receiver_info.get("city"):
					update_fields["recipient_city"] = receiver_info.get("city")
				if receiver_info.get("address_line1") or receiver_info.get("address_lines"):
					addr_lines = receiver_info.get("address_lines") or []
					if not addr_lines and receiver_info.get("address_line1"):
						addr_lines = [receiver_info.get("address_line1")]
					if receiver_info.get("address_line2"):
						addr_lines.append(receiver_info.get("address_line2"))
					update_fields["recipient_address"] = "\n".join(addr_lines) if addr_lines else ""

			# 如果已有实例但尚未设置 logistics_status_code，则可用本次云途返回状态做一次初始化
			if status_code and not getattr(doc, "logistics_status_code", None):
				update_fields["logistics_status_code"] = status_code

			# 使用 db_set 更新字段
			for field, value in update_fields.items():
				if doc.meta.has_field(field):
					doc.db_set(field, value, update_modified=False)

			frappe.db.commit()
			return {"success": True, "los_name": doc.name, "message": "已更新 Logistics Order Inst"}

		else:
			# 创建新记录
			# 获取销售订单信息
			try:
				sales_order = frappe.get_doc("Sales Order", sales_order_name)
			except frappe.DoesNotExistError:
				return {"success": False, "los_name": None, "message": f"销售订单不存在: {sales_order_name}"}

			doc = frappe.new_doc("Logistics Order Inst")
			doc.sales_order = sales_order_name
			doc.company = sales_order.company
			
			if waybill_number:
				doc.waybill_number = waybill_number
			if tracking_number:
				doc.tracking_number = tracking_number
			if product_code:
				doc.yt_product_code = product_code
			if warehouse_weight is not None:
				doc.warehouse_weight = float(warehouse_weight)
			
			# 收件人信息
			if receiver_info:
				if receiver_info.get("name") or receiver_info.get("first_name"):
					doc.recipient_name = (
						receiver_info.get("name") 
						or f"{receiver_info.get('first_name', '')} {receiver_info.get('last_name', '')}".strip()
					)
				if receiver_info.get("phone") or receiver_info.get("phone_number"):
					doc.recipient_phone = receiver_info.get("phone") or receiver_info.get("phone_number")
				if receiver_info.get("email") or receiver_info.get("email_id"):
					doc.recipient_email = receiver_info.get("email") or receiver_info.get("email_id")
				if receiver_info.get("country") or receiver_info.get("country_code"):
					doc.recipient_country = receiver_info.get("country") or receiver_info.get("country_code")
				if receiver_info.get("state") or receiver_info.get("province"):
					doc.recipient_state = receiver_info.get("state") or receiver_info.get("province")
				if receiver_info.get("city"):
					doc.recipient_city = receiver_info.get("city")
				if receiver_info.get("address_line1") or receiver_info.get("address_lines"):
					addr_lines = receiver_info.get("address_lines") or []
					if not addr_lines and receiver_info.get("address_line1"):
						addr_lines = [receiver_info.get("address_line1")]
					if receiver_info.get("address_line2"):
						addr_lines.append(receiver_info.get("address_line2"))
					doc.recipient_address = "\n".join(addr_lines) if addr_lines else ""
			
			# 初始化：Issue 状态设为空，避免默认值被统计为异常
			if hasattr(doc, "issue_status"):
				doc.issue_status = ""

			# 初始化：根据当前云途包裹状态设置 logistics_status_code
			if status_code and hasattr(doc, "logistics_status_code"):
				doc.logistics_status_code = status_code

			# 目的地国家：使用 Sales Order 上自定义的 country 字段（custom_country）
			delivery_country = getattr(sales_order, "custom_country", None)
			if delivery_country:
				doc.destination_country = delivery_country

			# 创建时间
			doc.creation_time = frappe.utils.now()

			doc.insert(ignore_permissions=True)
			frappe.db.commit()
			return {"success": True, "los_name": doc.name, "message": "已创建 Logistics Order Inst"}

	except Exception as e:
		frappe.logger().error(f"[LOS] 创建/更新失败: {str(e)}")
		import traceback
		frappe.logger().error(f"[LOS] 异常堆栈: {traceback.format_exc()}")
		return {"success": False, "los_name": None, "message": f"创建/更新失败: {str(e)}"}


def update_tracking_info_for_los(
	los_name: str | None = None,
	sales_order_name: str | None = None,
	order_number: str | None = None,
):
	"""
	更新 Logistics Order Inst 的轨迹信息
	
	Args:
		los_name: Logistics Order Inst 记录名称（优先使用）
		sales_order_name: 销售订单名称（如果未提供 los_name）
		order_number: 运单号/跟踪号（如果未提供，从记录中读取）
	
	Returns:
		dict: {"success": bool, "message": str}
	"""
	try:
		# 获取 Logistics Order Inst 记录
		if los_name:
			doc = frappe.get_doc("Logistics Order Inst", los_name)
		elif sales_order_name:
			existing = frappe.get_all(
				"Logistics Order Inst",
				filters={"sales_order": sales_order_name},
				fields=["name"],
				limit=1,
			)
			if not existing:
				return {"success": False, "message": f"未找到销售订单 {sales_order_name} 对应的 Logistics Order Inst 记录"}
			doc = frappe.get_doc("Logistics Order Inst", existing[0].name)
		else:
			return {"success": False, "message": "缺少 los_name 或 sales_order_name"}

		# 获取订单号用于查询轨迹
		ord_no = (order_number or "").strip()
		if not ord_no:
			ord_no = (doc.get("waybill_number") or "").strip()
		if not ord_no:
			ord_no = (doc.get("tracking_number") or "").strip()

		if not ord_no:
			return {"success": False, "message": "未找到可用于查询轨迹的订单号码"}

		# 处理 FG 前缀（转换为 YT 查询）
		query_tracking_number = ord_no
		if len(ord_no) >= 2 and ord_no[:2].upper() == "FG":
			query_tracking_number = "YT" + ord_no[2:]

		# 查询轨迹
		yt = YT()
		result = yt.get_order_track(query_tracking_number)

		if not result.get("success"):
			return {"success": False, "message": f"查询轨迹失败: {result.get('msg', '未知错误')}"}

		# 解析轨迹数据（使用 api.py 中的解析函数）
		from . import api as api_module
		
		# 解析轨迹信息
		tracking_info = api_module.parse_tracking_info(result)
		tracking_history = api_module.parse_tracking_history(result)

		# 获取原始返回数据
		result_list = result.get("result", [])
		yt_detail_data = result_list[0] if result_list else {}

		# 提取关键信息
		track_info = yt_detail_data.get("track_Info", {}) if isinstance(yt_detail_data, dict) else {}
		track_events = track_info.get("track_events", [])
		
		# 获取关键节点事件时间（订单创建 / 入仓 / 投递 / 退回 / 取消）
		def _parse_yt_time(value: str | None):
			"""兼容云途时间格式，统一转为 MySQL 可接受的时间字符串。

			云途返回可能是：
			- "2025-11-18T10:53:05+00:00"
			- "2025-11-18T10:53:05Z"
			- "2025-11-18 10:53:05"
			"""
			if not value:
				return None
			# 去掉时区、T、毫秒等，转成 MySQL 可接受的 "YYYY-MM-DD HH:MM:SS" 格式字符串
			cleaned = (
				value.replace("T", " ")
				.split(".")[0]
				.split("+")[0]
				.split("Z")[0]
				.strip()
			)
			return cleaned or None

		def _first_event_time(node_codes):
			if not track_events:
				return None
			for ev in track_events:
				code = (ev.get("track_node_code") or "").upper()
				if code in node_codes and ev.get("process_time"):
					dt = _parse_yt_time(ev.get("process_time"))
					if dt:
						return dt
			return None

		# 订单创建时间
		order_created_dt = _first_event_time({"ORDER_CREATION"})
		# 入仓 / 揽收时间（PICKED_UP）
		picked_up_dt = _first_event_time({"PICKED_UP"})
		# 成功投递时间（DELIVERED）
		delivered_dt = _first_event_time({"DELIVERED"})
		# 退回时间（RETURNED）
		returned_dt = _first_event_time({"RETURNED", "RETURNED_TO_SENDER"})
		# 取消时间（CANCELLED）
		cancelled_dt = _first_event_time({"CANCELLED", "ORDER_CANCELLED"})

		# 获取最后事件
		last_event = None
		last_event_time = None
		if track_events and len(track_events) > 0:
			# 按时间排序，取最后一个
			sorted_events = sorted(track_events, key=lambda x: x.get("process_time", ""))
			last_event = sorted_events[-1]
			last_event_time = last_event.get("process_time")
		
		# 获取包裹状态
		package_status = track_info.get("package_status", yt_detail_data.get("package_status", ""))
		
		# 映射云途包裹状态代码到我们内部的 logistics_status_code（保持原始字母，但注释按官方说明补全）：
		# N: 未找到订单
		# F: 预报已接收
		# T: 运输中
		# P: 处理中
		# D: 成功投递
		# E: 异常
		# R: 退回
		# C: 取消
		status_code_map = {
			"N": "N",  # 未找到订单
			"F": "F",  # 预报已接收
			"T": "T",  # 运输中
			"P": "P",  # 处理中
			"E": "E",  # 异常
			"R": "R",  # 退回
			"C": "C",  # 取消
			"D": "D",  # 成功投递
		}
		logistics_status_code = status_code_map.get(package_status, "")

		# 更新字段
		update_fields = {}
		
		# 轨迹历史 JSON
		if tracking_history:
			try:
				track_history_json = json.dumps(tracking_history, ensure_ascii=False)
				update_fields["track_history_json"] = track_history_json
			except Exception:
				pass

		# 最后事件信息
		if last_event:
			update_fields["last_event_text"] = last_event.get("process_content", "")
			if last_event_time:
				dt = _parse_yt_time(last_event_time)
				if dt:
					update_fields["last_event_time"] = dt
			update_fields["track_node_code"] = last_event.get("track_node_code", "")

		# 物流状态代码
		if logistics_status_code:
			update_fields["logistics_status_code"] = logistics_status_code

		# YT 详情 JSON（完整原始数据）
		if yt_detail_data:
			try:
				yt_detail_json = json.dumps(yt_detail_data, ensure_ascii=False)
				update_fields["yt_detail_json"] = yt_detail_json
			except Exception:
				pass

		# 轨迹最后更新时间
		update_fields["track_last_updated"] = frappe.utils.now()

		# 订单创建时间
		if order_created_dt:
			update_fields["creation_time"] = order_created_dt
		# 入仓时间（PICKED_UP）
		if picked_up_dt:
			update_fields["time_of_receipt"] = picked_up_dt
		# 成功投递时间（DELIVERED）
		if delivered_dt:
			update_fields["delivery_time"] = delivered_dt
		# 退回时间
		if returned_dt:
			update_fields["return_time"] = returned_dt
		elif package_status == "R" and last_event_time:
			dt = _parse_yt_time(last_event_time)
			if dt:
				update_fields["return_time"] = dt
		# 取消时间
		if cancelled_dt:
			update_fields["cancel_time"] = cancelled_dt
		elif package_status == "C" and last_event_time:
			dt = _parse_yt_time(last_event_time)
			if dt:
				update_fields["cancel_time"] = dt

		# 更新 POD 相关信息（如果有）
		if last_event and last_event.get("pod_url"):
			update_fields["pod_url"] = last_event.get("pod_url")
			if last_event.get("process_time"):
				dt = _parse_yt_time(last_event.get("process_time"))
				if dt:
					update_fields["pod_time"] = dt

		# 交付时间已优先从 DELIVERED 节点获取；若状态为成功投递 D 但未解析到 DELIVERED 节点，则回退使用最后事件时间
		if logistics_status_code == "D" and not delivered_dt and last_event_time:
			dt = _parse_yt_time(last_event_time)
			if dt:
				update_fields["delivery_time"] = dt

		# 使用 db_set 更新字段
		for field, value in update_fields.items():
			if doc.meta.has_field(field):
				doc.db_set(field, value, update_modified=False)

		frappe.db.commit()

		# 只要轨迹中出现过 PICKED_UP 节点，就触发正式计费逻辑（幂等，内部会检查 billed_flag）
		# 注意：不要求最后一个节点是 PICKED_UP，比如 PICKED_UP 之后又有中转、投递等节点，也要计费
		if picked_up_dt:
			try:
				from . import api_logistics_charging as charging_mod

				frappe.logger().error(
					f"[LOS Tracking] 准备触发正式计费: los={doc.name}, so={doc.sales_order}, "
					f"waybill={doc.waybill_number or doc.tracking_number}"
				)
				result = charging_mod.finalize_logistics_billing_for_los(doc.name)
				frappe.logger().error(
					f"[LOS Tracking] 正式计费结果: los={doc.name}, result={result}"
				)
			except Exception as e:
				frappe.logger().error(f"[LOS Tracking] 触发正式计费失败: {str(e)}")

		return {
			"success": True,
			"message": "轨迹信息更新成功",
			"data": {
				"logistics_status_code": logistics_status_code,
				"last_event_text": update_fields.get("last_event_text"),
				"track_node_code": update_fields.get("track_node_code"),
			},
		}

	except Exception as e:
		frappe.logger().error(f"[LOS Tracking] 更新轨迹失败: {str(e)}")
		import traceback
		frappe.logger().error(f"[LOS Tracking] 异常堆栈: {traceback.format_exc()}")
		return {"success": False, "message": f"更新轨迹失败: {str(e)}"}


@frappe.whitelist()
def sync_logistics_tracking(batch_size: int | None = 500):
	"""
	定时任务入口：
	- 从 Logistics Order Inst 表中查找需要跟踪的订单（有运单号 / 跟踪号）
	- 逐条调用 update_tracking_info_for_los，刷新轨迹、最后事件、POD 等信息
	- 默认遍历当前表中所有“未完结”的物流记录（按批次扫描），直到订单物流状态变为完成/关闭

	可以在 scheduler 里配置路径：
	- 方法路径: upsystem.upsystem.api_traking.sync_logistics_tracking
	"""
	try:
		# 单批大小控制扫描压力（只影响一次扫描处理多少条）
		try:
			batch_size = int(batch_size or 500)
		except Exception:
			batch_size = 500
		if batch_size <= 0:
			batch_size = 500
		if batch_size > 2000:
			batch_size = 2000

		# 后续如需扩展，可把 F/R 也加入这个集合
		closed_status = {"C", "D"}  # 已取消 / 已投递成功，不再跟踪

		scanned = 0
		updated = 0

		start = 0
		while True:
			# 使用原生 SQL，避免 filters 组合的兼容性问题
			rows = frappe.db.sql(
				"""
				SELECT
					name,
					sales_order,
					waybill_number,
					tracking_number,
					logistics_status_code,
					track_last_updated
				FROM `tabLogistics Order Inst`
				WHERE
					IFNULL(waybill_number, '') != '' OR IFNULL(tracking_number, '') != ''
				ORDER BY creation ASC
				LIMIT %(limit)s OFFSET %(offset)s
				""",
				{"limit": batch_size, "offset": start},
				as_dict=True,
			)

			if not rows:
				break

			for row in rows:
				scanned += 1

				# 已经是关闭状态（取消 / 已投递）的记录不再跟踪
				raw_code = (row.get("logistics_status_code") or "").strip()
				code = (raw_code.split(":", 1)[0] or "").strip().upper()
				if code in closed_status:
					continue

				res = update_tracking_info_for_los(los_name=row["name"])
				if isinstance(res, dict):
					if res.get("success"):
						updated += 1
					else:
						frappe.logger().error(
							f"[LOS Tracking] 单条更新失败: los={row.get('name')}, so={row.get('sales_order')}, "
							f"waybill={row.get('waybill_number') or row.get('tracking_number')}, "
							f"message={res.get('message')}"
						)
				else:
					frappe.logger().error(
						f"[LOS Tracking] 单条更新返回非 dict: los={row.get('name')}, result={res}"
					)

			start += batch_size

		frappe.logger().info(
			f"[LOS Tracking] 批量同步完成: scanned={scanned}, updated={updated}, batch_size={batch_size}"
		)

		return {
			"success": True,
			"scanned": scanned,
			"updated": updated,
			"batch_size": batch_size,
		}
	except Exception as e:
		frappe.logger().error(f"[LOS Tracking] 批量同步失败: {str(e)}")
		import traceback
		frappe.logger().error(f"[LOS Tracking] 批量同步异常堆栈: {traceback.format_exc()}")
		return {"success": False, "message": f"批量同步失败: {str(e)}"}

